from general_utilities import *
from glyphs import *
from actions import *

def upgraded_version(power):
  if power in power_upgrades_to:
    return power_upgrades_to[power]
  else:
    return None

power_descriptions = {}
power_names = {}
power_requires = {} # Not respected by item grants. Can be a vector of P_ codes, or can be missing from the dictionary.
power_not_in_first_n = {} # Not respected by item grants. Can be a whole number, or can be missing from the dictionary.
power_upgrades_to = {} # Can be a P_ code, or can be missing from the dictionary entirely.
powers_physical = {} # 1 or 0 - default is 1
power_requires_not = {} # Can be a vector of P_ codes, or can be missing from the dictionary entirely.

power_names[P_SMITE] = "Smite"
power_descriptions[P_SMITE] = "Double-click on an enemy next to you to launch a slow but extra-powerful Smite attack." 
power_upgrades_to[P_SMITE] = P_SUPER_SMITE
powers_physical[P_SMITE] = 1
  
power_names[P_CLEAVE] = "Cleave"
power_descriptions[P_CLEAVE] = "Quickly click on three adjacent spaces, to attack all of them with a single blow!"
power_upgrades_to[P_CLEAVE] = P_SUPER_CLEAVE
powers_physical[P_CLEAVE] = 1

power_names[P_JAB] = "Jab"
power_descriptions[P_JAB] = "Click on an enemy up to two spaces away, to hit them with a long-range thrust attack before they reach you!"
power_upgrades_to[P_JAB] = P_SUPER_JAB
powers_physical[P_JAB] = 1

power_names[P_ALL_OUT_DEFENSE] = "Improved Defense"
power_descriptions[P_ALL_OUT_DEFENSE] = "Click on yourself to start defending. For the next few turns, you will have a chance of blocking enemy attacks."
power_not_in_first_n[P_ALL_OUT_DEFENSE] = 1
powers_physical[P_ALL_OUT_DEFENSE] = 1

power_names[P_FIGHTING_WITHDRAWAL] = "Fighting Withdrawal"
power_descriptions[P_FIGHTING_WITHDRAWAL] = "Click on yourself and then on an adjacent spot, to take a step while defending yourself from enemy attacks."
power_requires[P_FIGHTING_WITHDRAWAL] = [P_ALL_OUT_DEFENSE]
powers_physical[P_FIGHTING_WITHDRAWAL] = 1

power_names[P_CHARGE] = "Charge"
power_descriptions[P_CHARGE] = "Drag the mouse from yourself to an enemy up to three spaces away, to quickly rush at them and attack!"
power_requires[P_CHARGE] = [P_SMITE]
powers_physical[P_CHARGE] = 1

power_names[P_SUPER_CLEAVE] = "Great Cleave"
power_descriptions[P_SUPER_CLEAVE] = "An improved version of Cleave. Quickly click on five adjacent spaces, to attack all of them with a single enormous blow!"
powers_physical[P_SUPER_CLEAVE] = 1

power_names[P_SUPER_SMITE] = "Super Smite"
power_descriptions[P_SUPER_SMITE] = "An improved version of Smite. Triple-click on an adjacent enemy to land a truly huge blow!"
powers_physical[P_SUPER_SMITE] = 1

power_names[P_SUPER_JAB] = "Long Jab"
power_descriptions[P_SUPER_JAB] = "An improved version of Jab. Click on an enemy up to three spaces away, to hit them with an extra long-range thrust attack."
powers_physical[P_SUPER_JAB] = 1

power_names[P_EASY_JUMP] = "Jump"
power_descriptions[P_EASY_JUMP] = "Click on the far side of an obstacle next to you, to jump over it. All characters can try to do this move, but the Jump power lets you get it right every time."
powers_physical[P_EASY_JUMP] = 1

power_names[P_LONG_JUMP] = "Long Jump"
power_descriptions[P_LONG_JUMP] = "Click on a spot three spaces away, to do a double-length jump!"
power_requires[P_LONG_JUMP] = [P_EASY_JUMP]
powers_physical[P_LONG_JUMP] = 1

power_names[P_DODGE] = "Dodging"
power_descriptions[P_DODGE] = "Click to your left, to your right, to your left again. For the next few turns, you will have a good chance of dodging attacks aimed at you."
power_not_in_first_n[P_DODGE] = 1
powers_physical[P_DODGE] = 0.5

power_names[P_PICK_LOCKS] = "Pick Locks"
power_descriptions[P_PICK_LOCKS] = "Walk up to a locked door and double-click on it to attempt to pick the lock. Not always successful, but definitely worth a try. If you fail, the lock will be jammed and you will not be able to open it until you find the key."
power_not_in_first_n[P_PICK_LOCKS] = 1
powers_physical[P_PICK_LOCKS] = 0.5

power_names[P_DOUBLE_SHOT] = "Double-Shot"
power_descriptions[P_DOUBLE_SHOT] = "If you are wielding a bow or crossbow, you can double-click on an enemy to shoot two missiles at them in rapid succession."
power_requires_not[P_DOUBLE_SHOT] = [P_FIREBOLT]
power_upgrades_to[P_DOUBLE_SHOT] = P_TRIPLE_SHOT
powers_physical[P_DOUBLE_SHOT] = 1

power_names[P_TRIPLE_SHOT] = "Triple-Shot"
power_descriptions[P_TRIPLE_SHOT] = "Triple-click on an enemy to shoot three missiles at them in rapid succession!"
powers_physical[P_TRIPLE_SHOT] = 1

power_names[P_SNEAK] = "Sneaking"
power_descriptions[P_SNEAK] = "Click on the space to your right, then to the right of that, then to the right again. This will shift you into 'sneak mode' - you will be slow and stealthy until the next time you get into a fight."
power_not_in_first_n[P_SNEAK] = 1
powers_physical[P_SNEAK] = 0.5

power_names[P_BLINK] = "Blink"
power_descriptions[P_BLINK] = "Click on the spaces immediately to the northwest, northeast, southeast, and southwest of you, in that order. You will 'blink' out of existence and reappear somewhere else nearby."
power_not_in_first_n[P_BLINK] = 1
powers_physical[P_BLINK] = 0

power_names[P_TELEPORTATION] = "Teleportation"
power_descriptions[P_TELEPORTATION] = "As per Blink, click on the spaces immediately to the northwest, northeast, southeast, and southwest of you, in that order. Then drag the mouse from yourself to the spot you wish to teleport to. You will disappear and reappear there!"
power_requires[P_TELEPORTATION] = [P_BLINK]
powers_physical[P_TELEPORTATION] = 0

power_names[P_FIREBOLT] = "Firebolt"
power_descriptions[P_FIREBOLT] = "Double-click on an enemy at long range, to launch a Firebolt at them."
power_requires_not[P_FIREBOLT] = [P_DOUBLE_SHOT]
powers_physical[P_FIREBOLT] = 0

power_names[P_CONFUSION] = "Confusion"
power_descriptions[P_CONFUSION] = "Draw a circle around an enemy at long range, to make them fall under a Confusion spell. Beware, some enemies are immune."
powers_physical[P_CONFUSION] = 0

power_names[P_LIGHTNING_BOLT] = "Lightning Bolt"
power_descriptions[P_LIGHTNING_BOLT] = "Click on yourself and drag the mouse in a jagged line from you to an enemy at long range, to launch a powerful bolt of lightning at them."
power_requires[P_LIGHTNING_BOLT] = [P_FIREBOLT]
powers_physical[P_LIGHTNING_BOLT] = 0

power_names[P_WINDBLAST] = "Gust of Wind"
power_descriptions[P_WINDBLAST] = "Click on yourself, hold the mouse button down and drag the mouse in a straight line to your enemy. A hurricane-force gust of wind will strike them, potentially driving smaller enemies several paces back."
powers_physical[P_WINDBLAST] = 0

power_names[P_CLAIRVOYANCE] = "Clairvoyance"
power_descriptions[P_CLAIRVOYANCE] = "Double-click on one of the outer walls of the room you are in, to get a magical view of the next room in that direction."
power_not_in_first_n[P_CLAIRVOYANCE] = 1
powers_physical[P_CLAIRVOYANCE] = 0

power_names[P_EXORCISM] = "Exorcism"
power_descriptions[P_EXORCISM] = "Make the sign of the cross by clicking four times around yourself - to the north, to the south, the west, and finally the east. This will exorcise nearby undead monsters!"
power_not_in_first_n[P_EXORCISM] = 1
powers_physical[P_EXORCISM] = 0

power_names[P_HEALING] = "Healing"
power_descriptions[P_HEALING] = "Click on your health bar to heal some of your wounds. Beware, Healing will not completely restore your vitality."
powers_physical[P_HEALING] = 0.5

power_names[P_CIRCLE_OF_PROTECTION] = "Circle of Protection"
power_descriptions[P_CIRCLE_OF_PROTECTION] = "Draw a circle around yourself to create magical protection, which helps you resist enemy spells and deflect enemy attacks."
powers_physical[P_CIRCLE_OF_PROTECTION] = 0

power_names[P_ADMONISHMENT] = "Admonishment"
power_descriptions[P_ADMONISHMENT] = "Click the mouse on an enemy and drag it right off the screen! This reproachful sermon will put holy dread into them and make them retreat."
power_not_in_first_n[P_ADMONISHMENT] = 1
powers_physical[P_ADMONISHMENT] = 0.5

power_names[P_WALK_THROUGH_FIRE] = "Walk Through Fire"
power_descriptions[P_WALK_THROUGH_FIRE] = "Double-click on a burning fire, to gain the ability to walk through flames without being harmed. This spell will also give you some resistance to other fire-based attacks."
power_not_in_first_n[P_WALK_THROUGH_FIRE] = 1
powers_physical[P_WALK_THROUGH_FIRE] = 0.5

power_names[P_DETECT_ENEMIES] = "Detect Enemies"
power_descriptions[P_DETECT_ENEMIES] = "This spell allows you to sense where danger lies in wait. Clicking the Map button will now enable you to see all enemies on the level."
power_not_in_first_n[P_DETECT_ENEMIES] = 1
powers_physical[P_DETECT_ENEMIES] = 0

power_names[P_CIRCLE_OF_FIRE] = "Ring of Fire"
power_descriptions[P_CIRCLE_OF_FIRE] = "(The ring of fire... The ring of fire...) Double-click on yourself then quickly draw a small circle around yourself to create a ring of fire. This will ward off most enemies. It can burn you though, so if you are not fire-resistant, wait until the fire burns out before moving away."
powers_physical[P_CIRCLE_OF_FIRE] = 0

def activate_power(d,display,glyphs,power):
  # returns a triple of: valid, array_of_actions, n_glyphs_to_remove
  no_match = (0,[],0)
  if len(glyphs)>0:
    last_glyph = glyphs[0]
  else:
    last_glyph = None
  if len(glyphs)>1:
    second_last_glyph = glyphs[1]
  else:
    second_last_glyph = None
  character = d.get_character()
  active_pos = d.get_active_pos()
  if power==P_SMITE:
    if second_last_glyph is not None and is_double_click(last_glyph,second_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if not(pos_clicked is None) and adjacent(d,active_pos,pos_clicked):
        occupant = d.get_occupant(pos_clicked)
        if not(occupant is None) and character.is_enemy(occupant):
          return (1,[Action(character,A_SMITE,pos_clicked,occupant)],2)
    return no_match
  elif power==P_SUPER_SMITE:
    if len(glyphs)>2:
      third_last_glyph = glyphs[2]
    else:
      return no_match
    if third_last_glyph is not None and second_last_glyph is not None and is_triple_click(last_glyph,second_last_glyph,third_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
      if not(pos_clicked is None) and adjacent(d,active_pos,pos_clicked):
        occupant = d.get_occupant(pos_clicked)
        if not(occupant is None) and character.is_enemy(occupant):
          return (1,[Action(character,A_SUPER_SMITE,pos_clicked,occupant)],3)
    return no_match
  elif power==P_CLEAVE or power==P_SUPER_CLEAVE:
    attacks = []
    if power==P_CLEAVE:
      sequence_length = 3
    else:
      sequence_length = 5
    if len(glyphs)<sequence_length:
      return no_match
    recent_glyphs = glyphs[:sequence_length]
    posses_clicked = []
    last_click = None
    for i in range(0,len(recent_glyphs)):
      glyph = recent_glyphs[len(recent_glyphs)-1-i]
      if is_click(glyph):
        pos_clicked = mouse_pos_to_map_pos(d,display,glyph.last_pos)
        if pos_clicked is None:
          return no_match
        elif pos_clicked in posses_clicked:
          return no_match
        else:
          posses_clicked.append(pos_clicked.copy())
        if last_click is not None and not adjacent(d,last_click,pos_clicked):
          return no_match
        else:
          last_click = pos_clicked.copy()
        if pos_clicked is not None and adjacent(d,active_pos,pos_clicked):
          occupant = d.get_occupant(pos_clicked)
          if not(occupant is None):
            if not character.is_enemy(occupant):
              return no_match
            else:
              #if len(attacks)==0:
              #  action_type = A_CLEAVE
              #else:
              #  action_type = A_SILENT_CLEAVE   not so good if first attack misses
              action_type = A_CLEAVE
              action = Action(character,action_type,pos_clicked,occupant)
              action.energy_multiplier = 0
              attacks.append(action)            
        else:
          return no_match
      else:
        return no_match
    if len(attacks)==0:
      return no_match
    lose_time = Action(character,A_NOTHING,None,None)
    lose_time.energy_multiplier = character.info().melee_cost
    attacks.append(lose_time)
    return (1,attacks,sequence_length)
  elif power==P_JAB or power==P_SUPER_JAB:
    if is_click(last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(pos_clicked is None):
        occupant = d.get_occupant(pos_clicked)
        if occupant is not None and character.is_enemy(occupant):
          this_range = active_pos.max_orthogonal_distance_within_room(pos_clicked)
          if (power==P_JAB and this_range<>2) or (power==P_SUPER_JAB and (this_range<2 or this_range>3)):
            return no_match
          return (1,[Action(character,A_JAB,pos_clicked,occupant)],1)
    return no_match  
  elif power==P_ALL_OUT_DEFENSE:
    if is_click(last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(pos_clicked is None) and pos_clicked==active_pos and not d.l.tile_at(pos_clicked).info().is_portal:
        return (1,[Action(character,A_ALL_OUT_DEFENSE,pos_clicked,character)],1)
    return no_match         
  elif power==P_FIGHTING_WITHDRAWAL:
    if not (second_last_glyph is None) and is_click(second_last_glyph):
      second_last_pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if not(second_last_pos_clicked is None) and second_last_pos_clicked==active_pos:
        if is_click(last_glyph):
          last_pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
          if not(last_pos_clicked is None) and adjacent(d,last_pos_clicked,active_pos):
            occupant = d.get_occupant(last_pos_clicked)
            if occupant is None:
              if d.l.tile_at(last_pos_clicked).info().walkable(character):
                action1 = Action(character,A_WALK,last_pos_clicked.copy(),None)
                action1.energy_multiplier = 0
                action2 = Action(character,A_ALL_OUT_DEFENSE,last_pos_clicked.copy(),character)
                return (1,[action1,action2],2)
    return no_match
  elif power==P_CHARGE:
    if is_straight_line(last_glyph):
      line_start = mouse_pos_to_map_pos(d,display,last_glyph.first_pos)
      line_end = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(line_start is None) and not (line_end is None) and line_start==active_pos:
        this_range = min(abs(active_pos.down-line_end.down),abs(active_pos.right-line_end.right)) 
        if this_range>3:
          return no_match
        occupant = d.get_occupant(line_end)
        if not(occupant is None) and character.is_enemy(occupant):
          actions = []
          posses_considered = []
          for right_down in last_glyph.pos_series:
            this_pos = mouse_pos_to_map_pos(d,display,right_down)
            if this_pos is not None and this_pos<>active_pos and this_pos<>line_end:
              this_occupant = d.get_occupant(this_pos)
              if this_occupant is not None or not d.l.tile_at(this_pos).info().walkable(character):
                return no_match
            if not this_pos in posses_considered:
              if this_pos==line_end:
                actions.append(Action(character,A_HIT,line_end,occupant))
              else:
                move_action = Action(character,A_WALK,this_pos,None)
                move_action.energy_multiplier = 0
                actions.append(move_action)
              posses_considered.append(this_pos)
          return (1,actions,1)
    return no_match  
  elif power==P_WINDBLAST:
    if is_straight_line(last_glyph):
      line_start = mouse_pos_to_map_pos(d,display,last_glyph.first_pos)
      line_end = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(line_start is None) and not (line_end is None) and line_start==active_pos:
        this_range = min(abs(active_pos.down-line_end.down),abs(active_pos.right-line_end.right)) 
        occupant = d.get_occupant(line_end)
        if not(occupant is None) and character.is_enemy(occupant):
          actions = []
          posses_considered = []
          for right_down in last_glyph.pos_series:
            this_pos = mouse_pos_to_map_pos(d,display,right_down)
            if this_pos is not None and this_pos<>active_pos and this_pos<>line_end:
              if not d.l.tile_at(this_pos).info().walkable(character):
                return no_match
          actions.append(Action(character,A_WINDBLAST,line_end,occupant))
          return (1,actions,1)
    return no_match  
  elif power==P_HARD_JUMP or power==P_EASY_JUMP:
    if is_click(last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if pos_clicked is not None:
        this_range = active_pos.max_orthogonal_distance_within_room(pos_clicked)
        if this_range==2:
          dir = active_pos.direction_towards(pos_clicked)
          if dir is not None:
            intermediate_step = get_pos_in_dir(d,active_pos,dir)
            occupant1 = d.get_occupant(intermediate_step)
            occupant2 = d.get_occupant(pos_clicked)
            if occupant1 is None and occupant2 is None:
              if d.l.tile_at(pos_clicked).info().leapable and d.l.tile_at(intermediate_step).info().leapable:
                speed = character.stats.speed
                if power==P_EASY_JUMP:
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)
                elif random.random()<speed/18.0:
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)
                elif random.random()<0.5 and d.tutorial:
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)
                elif random.random()<0.5 and character.use_zap(6,3):
                  n = Notification(character.pos)
                  n.set_message(character.name+" takes a running jump!",0)
                  n.play(d)
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)                  
                else:
                  n = Notification(character.pos)
                  n.set_message(character.name+" failed the jump...",0)
                  n.set_sound("cancel1")
                  n.play(d)
                  return (1,[Action(character,A_WALK,intermediate_step,character),Action(character,A_WALK,pos_clicked,character)],1)
    return no_match
  elif power==P_LONG_JUMP:
    if is_click(last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if pos_clicked is not None:
        this_range = active_pos.max_orthogonal_distance_within_room(pos_clicked)
        if this_range==3:
          dir = active_pos.direction_towards(pos_clicked)
          if dir is not None:
            intermediate_step_1 = get_pos_in_dir(d,active_pos,dir)
            intermediate_step_2 = get_pos_in_dir(d,intermediate_step_1,dir)
            occupant1 = d.get_occupant(intermediate_step_1)
            occupant2 = d.get_occupant(intermediate_step_2)
            occupant3 = d.get_occupant(pos_clicked)
            if occupant1 is None and occupant2 is None and occupant3 is None:
              if d.l.tile_at(pos_clicked).info().leapable and d.l.tile_at(intermediate_step_1).info().leapable and d.l.tile_at(intermediate_step_2).info().leapable:
                speed = character.stats.speed + 5
                if random.random()<speed/20.0:
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)
                elif random.random()<0.5 and character.use_zap(6,3):
                  n = Notification(character.pos)
                  n.set_message(character.name+" takes a running jump!",0)
                  n.play(d)
                  return (1,[Action(character,A_JUMP,pos_clicked,character)],1)                  
                else:
                  n = Notification(character.pos)
                  n.set_message(character.name+" failed the jump...",0)
                  n.set_sound("cancel1")
                  n.play(d)
                  return (1,[Action(character,A_WALK,intermediate_step_1,character),Action(character,A_WALK,intermediate_step_2,character),Action(character,A_WALK,pos_clicked,character)],1)
    return no_match    
  elif power==P_DODGE:
    if len(glyphs)>2:
      third_last_glyph = glyphs[2]
    else:
      return no_match
    if is_click(last_glyph) and is_click(second_last_glyph) and is_click(third_last_glyph):
      octant1 = octant_relative_to_you(d,display,last_glyph.last_pos)
      if octant1==DIR_LEFT:
        octant2 = octant_relative_to_you(d,display,second_last_glyph.last_pos)
        if octant2==DIR_RIGHT:
          octant3 = octant_relative_to_you(d,display,third_last_glyph.last_pos)
          if octant3==DIR_LEFT:
            return (1,[Action(character,A_DODGE,None,character)],3)
    return no_match    
  elif power==P_DOUBLE_SHOT or power==P_FIREBOLT:
    if second_last_glyph is not None and is_double_click(last_glyph,second_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if not(pos_clicked is None) and not adjacent(d,active_pos,pos_clicked):
        occupant = d.get_occupant(pos_clicked)
        if not(occupant is None) and character.is_enemy(occupant):
          if d.line_of_shot(character,pos_clicked,[]):
            if power==P_DOUBLE_SHOT:
              action1 = Action(character,A_SHOT,pos_clicked,occupant)
              action1.energy_multiplier = 0
              action2 = Action(character,A_SHOT,pos_clicked,occupant)
              return (1,[action1,action2],2)
            else:
              return (1,[Action(character,A_FIREBOLT,pos_clicked,occupant)],2)
    return no_match
  elif power==P_TRIPLE_SHOT:
    if len(glyphs)>2:
      third_last_glyph = glyphs[2]
    else:
      return no_match
    if third_last_glyph is not None and second_last_glyph is not None and is_triple_click(last_glyph,second_last_glyph,third_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
      if not(pos_clicked is None) and not adjacent(d,active_pos,pos_clicked):
        occupant = d.get_occupant(pos_clicked)
        if not(occupant is None) and character.is_enemy(occupant):
          if d.line_of_shot(character,pos_clicked,[]):
            action1 = Action(character,A_SHOT,pos_clicked,occupant)
            action1.energy_multiplier = 0
            action2 = Action(character,A_SHOT,pos_clicked,occupant)
            action2.energy_multiplier = 0
            action3 = Action(character,A_SHOT,pos_clicked,occupant)
            return (1,[action1,action2,action3],3)
    return no_match
  elif power==P_SHOT or power==P_ZAP:
    if is_click(last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(pos_clicked is None) and not adjacent(d,active_pos,pos_clicked):
        occupant = d.get_occupant(pos_clicked)
        if not(occupant is None) and character.is_enemy(occupant):
          if d.line_of_shot(character,pos_clicked,[]):
            if power==P_SHOT:
              return (1,[Action(character,A_SHOT,pos_clicked,occupant)],1)
            elif power==P_ZAP:
              return (1,[Action(character,A_ZAP,pos_clicked,occupant)],1)
    return no_match
  elif power==P_WALK_THROUGH_FIRE: 
    if not character.has_condition(C_FIRERES_FROM_SPELL):
      if second_last_glyph is not None and is_double_click(last_glyph,second_last_glyph):
        pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
        if not(pos_clicked is None):
          occupant = d.get_occupant(pos_clicked)
          if (occupant is None):
            if d.l.tile_at(pos_clicked).info().is_fiery:
              return (1,[Action(character,A_FIRERES,None,character)],2)
    return no_match
  elif power==P_PICK_LOCKS: 
    if second_last_glyph is not None and is_double_click(last_glyph,second_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if not(pos_clicked is None) and adjacent(d,pos_clicked,character.pos):
          occupant = d.get_occupant(pos_clicked)
          if (occupant is None):
            if d.l.tile_at(pos_clicked).info().can_be_unlocked:
              jam_chance = {O_KEY_GOLD:1,O_KEY_SILVER:0.6,O_KEY_RED:0.45,O_KEY_GREEN:0.3,O_KEY_BLUE:0.1}
              if d.l.tile_at(pos_clicked).info().key_type is None or (random.random()>jam_chance[d.l.tile_at(pos_clicked).info().key_type] and not d.l.tile_at(pos_clicked).jammed):
                return (1,[Action(character,A_PICKLOCK,pos_clicked,None)],2)
              else:
                return (1,[Action(character,A_JAMLOCK,pos_clicked,None)],2)
    return no_match
  elif power==P_SNEAK:
    if len(glyphs)>2:
      third_last_glyph = glyphs[2]
    else:
      return no_match
    if is_click(last_glyph) and is_click(second_last_glyph) and is_click(third_last_glyph):
      last_click_pos = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if last_click_pos is not None and active_pos.distance_within_room(last_click_pos)==3 and active_pos.exact_step_direction(last_click_pos)==DIR_RIGHT:
        second_last_click_pos = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
        if second_last_click_pos is not None and active_pos.distance_within_room(second_last_click_pos)==2 and active_pos.exact_step_direction(second_last_click_pos)==DIR_RIGHT:
          third_last_click_pos = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
          if third_last_click_pos is not None and active_pos.distance_within_room(third_last_click_pos)==1 and active_pos.exact_step_direction(third_last_click_pos)==DIR_RIGHT:
            return (1,[Action(character,A_SNEAK,None,character)],3)
    return no_match    
  elif power==P_BLINK:
    if len(glyphs)>3:
      third_last_glyph = glyphs[2]
      fourth_last_glyph = glyphs[3]
    else:
      return no_match
    if is_click(last_glyph) and is_click(second_last_glyph) and is_click(third_last_glyph) and is_click(fourth_last_glyph):
      pos1 = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if pos1 is not None and pos1.right==active_pos.right-1 and pos1.down==active_pos.down+1:
        pos2 = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
        if pos2 is not None and pos2.right==active_pos.right+1 and pos2.down==active_pos.down+1:
          pos3 = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
          if pos3 is not None and pos3.right==active_pos.right+1 and pos3.down==active_pos.down-1:
            pos4 = mouse_pos_to_map_pos(d,display,fourth_last_glyph.last_pos)
            if pos4 is not None and pos4.right==active_pos.right-1 and pos4.down==active_pos.down-1:
              return (1,[Action(character,A_BLINK,None,character)],4)
    return no_match  
  elif power==P_TELEPORTATION:
    if len(glyphs)>4:
      third_last_glyph = glyphs[2]
      fourth_last_glyph = glyphs[3]
      fifth_last_glyph = glyphs[4]
    else:
      return no_match
    if is_click(second_last_glyph) and is_click(third_last_glyph) and is_click(fourth_last_glyph) and is_click(fifth_last_glyph):
      pos1 = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if pos1 is not None and pos1.right==active_pos.right-1 and pos1.down==active_pos.down+1:
        pos2 = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
        if pos2 is not None and pos2.right==active_pos.right+1 and pos2.down==active_pos.down+1:
          pos3 = mouse_pos_to_map_pos(d,display,fourth_last_glyph.last_pos)
          if pos3 is not None and pos3.right==active_pos.right+1 and pos3.down==active_pos.down-1:
            pos4 = mouse_pos_to_map_pos(d,display,fifth_last_glyph.last_pos)
            if pos4 is not None and pos4.right==active_pos.right-1 and pos4.down==active_pos.down-1:
              source_pos = mouse_pos_to_map_pos(d,display,last_glyph.first_pos)
              destination_pos = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
              if source_pos==active_pos and destination_pos<>active_pos:
                occupant = d.get_occupant(destination_pos)
                if occupant is None and d.l.tile_at(destination_pos).info().walkable(character):
                  return (1,[Action(character,A_TELEPORTATION,destination_pos,character)],5)
    return no_match  
  elif power==P_LIGHTNING_BOLT:
    if is_wiggly_line(last_glyph):
      start_pos = mouse_pos_to_map_pos(d,display,last_glyph.first_pos)
      end_pos = mouse_pos_to_map_pos(d,display,last_glyph.last_pos)
      if not(end_pos is None) and not adjacent(d,active_pos,end_pos):
        occupant = d.get_occupant(end_pos)
        if not(occupant is None) and character.is_enemy(occupant):
          if d.line_of_shot(character,end_pos,[]):
            return (1,[Action(character,A_LIGHTNING_BOLT,end_pos,occupant)],1)
    return no_match
  elif power==P_CONFUSION:
    if is_circle(last_glyph):
      if len(all_map_pos_in_circle(d,display,last_glyph))<=9:
        center_pos = mouse_pos_to_map_pos(d,display,center_mouse_pos(last_glyph))
        if not(center_pos is None) and not adjacent(d,active_pos,center_pos):
          occupant = d.get_occupant(center_pos)
          if not(occupant is None) and character.is_enemy(occupant):
            if d.line_of_shot(character,center_pos,[]):
              return (1,[Action(character,A_CONFUSION,center_pos,occupant)],1)
    return no_match
  elif power==P_CLAIRVOYANCE:
    if second_last_glyph is not None and is_double_click(last_glyph,second_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,second_last_glyph.last_pos)
      if not(pos_clicked is None):
        pos_in_room_to_view = pos_clicked.copy()
        dir = None
        if pos_clicked.down==0:
          dir = DIR_UP
          pos_in_room_to_view.r_down = pos_clicked.r_down-1
        elif pos_clicked.down==d.l.room_at(pos_clicked).height-1:
          dir = DIR_DOWN
          pos_in_room_to_view.r_down = pos_clicked.r_down+1
        elif pos_clicked.right==0:
          dir = DIR_LEFT
          pos_in_room_to_view.r_right = pos_clicked.r_right-1
        elif pos_clicked.right==d.l.room_at(pos_clicked).width-1:
          dir = DIR_RIGHT
          pos_in_room_to_view.r_right = pos_clicked.r_right+1
        if dir is not None and d.l.valid(pos_in_room_to_view):
          return (1,[Action(character,A_CLAIRVOYANCE,pos_in_room_to_view,character)],2)
    return no_match
  elif power==P_EXORCISM:
    if len(glyphs)>3:
      third_last_glyph = glyphs[2]
      fourth_last_glyph = glyphs[3]
    else:
      return no_match
    if is_click(last_glyph) and is_click(second_last_glyph) and is_click(third_last_glyph) and is_click(fourth_last_glyph):
      if quadrant_relative_to_you(d,display,fourth_last_glyph.last_pos)==DIR_UP:
        if quadrant_relative_to_you(d,display,third_last_glyph.last_pos)==DIR_DOWN:        
          if quadrant_relative_to_you(d,display,second_last_glyph.last_pos)==DIR_LEFT:
            if quadrant_relative_to_you(d,display,last_glyph.last_pos)==DIR_RIGHT:
              return (1,[Action(character,A_EXORCISM,None,character)],4)
    return no_match  
  elif power==P_CIRCLE_OF_PROTECTION:
    if is_circle(last_glyph):
      if len(all_map_pos_in_circle(d,display,last_glyph))<=25:
        center_pos = mouse_pos_to_map_pos(d,display,center_mouse_pos(last_glyph))
        if not(center_pos is None) and center_pos==active_pos:
          return (1,[Action(character,A_CIRCLE_OF_PROTECTION,center_pos,character)],1)
    return no_match
  elif power==P_CIRCLE_OF_FIRE:
    if len(glyphs)>2:
      third_last_glyph = glyphs[2]
    else:
      return no_match    
    if third_last_glyph is not None and second_last_glyph is not None and is_double_click(second_last_glyph,third_last_glyph):
      pos_clicked = mouse_pos_to_map_pos(d,display,third_last_glyph.last_pos)
      if not(pos_clicked is None) and pos_clicked==character.pos:
        if is_circle(last_glyph):
          if len(all_map_pos_in_circle(d,display,last_glyph))<=25:
            center_pos = mouse_pos_to_map_pos(d,display,center_mouse_pos(last_glyph))
            if not(center_pos is None) and center_pos==active_pos:
              return (1,[Action(character,A_CIRCLE_OF_FIRE,center_pos,character)],3)
    return no_match
  elif power==P_HEALING:
    if is_click(last_glyph):
      if is_on_health_bar(d,display,last_glyph.last_pos):
        return (1,[Action(character,A_HEALING,None,character)],1)
    return no_match
  elif power==P_ADMONISHMENT:
    if not is_click(last_glyph):
      start_pos = mouse_pos_to_map_pos(d,display,last_glyph.first_pos)
      if start_pos is not None and start_pos<>active_pos:
        occupant = d.get_occupant(start_pos)
        if occupant is not None and character.is_enemy(occupant):
          if mouse_pos_to_map_pos(d,display,last_glyph.last_pos) is None:
            return (1,[Action(character,A_ADMONISHMENT,start_pos,occupant)],1)
    return no_match  
  elif power==P_DETECT_ENEMIES:
    pass # handled in main input handler
  return no_match
